#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>

// 设备名
#define DEVNAME "newchr_led_drv"

// 字符设备结构体
struct newchrled
{
    struct cdev cdev;      // 设备节点
    dev_t devid;           // 设备号
    struct class *class;   // 设备类
    struct device *device; // 设备
    int major;             // 主设备号
    int minor;             // 次设备号
};

struct newchrled newchr_led; // 新字符led设备

/* 新字符设备驱动框架 */
//  寄存器声明
#define CCM_CCGR1_BASE (0x020C406C)         // 时钟配置
#define SW_MUX_GPIO1_IO03_BASE (0x020E0068) // 复用功能配置
#define SW_PAD_GPIO1_IO03_BASE (0x020E02F4) // 电气属性配置
#define GPIO1_DR_BASE (0x0209C000)          // 输出电平配置
#define GPIO1_GDIR_BASE (0x0209C004)        // 输出方向配置

// 转换后的地址
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

ssize_t newchr_led_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos)
{
    int ret, val;
    unsigned char mybuf[1];
    ret = copy_from_user(mybuf, buf, size);
    if (ret > 0)
    {
        printk("copy from user error\r\n");
        return -1;
    }

    if (mybuf[0] == '0')
    {
        printk("led close\r\n");
        // 输出高电平关闭灯
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
    else if (mybuf[0] == '1')
    {
        printk("led open\r\n");
        // 输出低电平打开灯
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }
    else
    {
        printk("cmd is error!\r\n");
    }
    return 0;
}

int newchr_led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchr_led;
    printk("led_drv open!\r\n");
    return 0;
}
int newchr_led_close(struct inode *inode, struct file *filp)
{
    struct newchrled *data = (struct newchrled *)filp->private_data;
    printk("data = %d\r\n", data->major);
    printk("led_drv close!\r\n");
    return 0;
}

// 字符设备操作集
static const struct file_operations newchr_fops = {
    .owner = THIS_MODULE,
    .open = newchr_led_open,
    .release = newchr_led_close,
    .write = newchr_led_write,
};

static int __init newchr_led_drv_init(void)
{
    int ret;
    int val;
    // 给定主设备号的情况下创建设备号
    if (newchr_led.major)
    {
        newchr_led.devid = MKDEV(newchr_led.major, 0);
        ret = register_chrdev_region(newchr_led.devid, 1, DEVNAME);
    }
    else
    {
        ret = alloc_chrdev_region(&newchr_led.devid, 0, 1, DEVNAME);
        newchr_led.major = MAJOR(newchr_led.devid);
        newchr_led.minor = MINOR(newchr_led.devid);
    }

    if (ret < 0)
    {
        printk("newchrled register err\r\n");
        return -1;
    }
    printk("newchr_led major = %d,minor = %d\r\n", newchr_led.major, newchr_led.minor);

    // 注册设备，关联fops
    newchr_led.cdev.owner = THIS_MODULE;
    cdev_init(&newchr_led.cdev, &newchr_fops);
    ret = cdev_add(&newchr_led.cdev, newchr_led.devid, 1);
    if (ret)
    {
        printk("cdev add err\r\n");
    }

    // 创建设备类
    newchr_led.class = class_create(THIS_MODULE, DEVNAME);
    if (IS_ERR(newchr_led.class))
        return PTR_ERR(newchr_led.class);

    // 创建设备节点
    newchr_led.device = device_create(newchr_led.class, NULL, newchr_led.devid, NULL, DEVNAME);
    if (IS_ERR(newchr_led.device))
        return PTR_ERR(newchr_led.device);

    // 初始化LED
    // 初始化LED相关寄存器
    CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    // 设置时钟
    val = readl(CCM_CCGR1);
    val &= ~(3 << 26);
    val |= 3 << 26;
    writel(val, CCM_CCGR1);

    // 设置IO复用
    writel(0x5, SW_MUX_GPIO1_IO03);
    // 设置电气属性
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    // 设置输出
    val = readl(GPIO1_GDIR);
    val |= 1 << 3;
    writel(val, GPIO1_GDIR);

    // 输出高电平关闭灯
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    return 0;
}

static void __exit newchr_led_drv_exit(void)
{
    device_destroy(newchr_led.class, newchr_led.devid);
    class_destroy(newchr_led.class);
    cdev_del(&newchr_led.cdev);
    // 注销设备号
    unregister_chrdev_region(newchr_led.devid, 1);
}

module_init(newchr_led_drv_init);
module_exit(newchr_led_drv_exit);
MODULE_LICENSE("GPL");
